跳到主要内容

Go 模拟 Java 的 try-catch

示例代码

设计思想

Go 语言处理 error 有个特点,就是当前层的 error 必须当前层处理完成,无法像 Java 那样遇到 Error 可以直接向上抛,实际上 Java 这种 Error 处理模式属于职责链模式,整个设计思想如下图所示:

solution2-zh.png

所以封装一个 Go 的 try-catch 就需要主动去处理调用链显示的问题,下面直接上代码量,核心逻辑就是通过 runtime.Caller 取得当前方法的调用栈,然后再稍加递归

编写异常包

package my_error

import (
"encoding/json"
"fmt"
"runtime"
"strings"
)

// StError 定义一个标准的错误接口
type StError interface {
Error() string
GetMsg() string
GetLevel() int
}

const (
LevelFatal = 1
LevelWarn = 2

skipLogCaller = 2
skipNewTradeErrCaller = 3
)

// levelMap 错误等级日志字符串map
//"level": levelMap[e.level]
var levelMap = map[int]string{
LevelFatal: "Fatal",
LevelWarn: "Warn",
}

func NewFatalErr(msg string, originErrList ...error) StError {
err := newStError(LevelFatal, msg, originErrList...)
err.genCallerInfo(skipNewTradeErrCaller)
return err
}

func NewWarnErr(msg string, originErrList ...error) StError {
err := newStError(LevelWarn, msg, originErrList...)
err.genCallerInfo(skipN// StError 定义一个标准的错误接口runtime.Callers
GetLevel() int
}
func newStError(level int, msg string, originErrList ...error) *customError {
tradeErr := &customError{
level: level,
msg: msg,
}
tradeErr.initLogMsgAndOriginErr(originErrList...)
return tradeErr
}

// initLogMsgAndOriginErr 初始化 msg 和 OriginErr
func (e *customError) initLogMsgAndOriginErr(originErrList ...error) {
// 把传入的异常添加进 OriginErr 异常链
for _, originErr := range originErrList {
err, ok := originErr.(*customError)
if ok {
e.originErrS = append(e.originErrS, err)
} else {
e.msg += originErr.Error()
}
}
}

type customError struct {
level int// StError 定义一个标准的错误接口
type StError interface {
Error() string
GetMsg() string
GetLevel() int
} //[]callerLogMsg
callerStackS []string //[]funcName
callerStackMap map[string]string //map[funcName]logMsg
originErrS []*customError
}

func (e *customError) GetMsg() string {
return e.msg
}// StError 定义一个标准的错误接口
type StError interface {
Error() string
GetMsg() string
GetLevel() int
}tCallerFuncNameAndLogStr(skipLogCaller)
// 根据这个方法名生成调用链日志
e.genCallerLogStr(funName, logStr)
list := e.getLogStrMapList()
bytes, _ := json.Marshal(list)
return string(bytes)
}

// genCallerInfo 生成调用栈的相关信息属性填充 callerStackMap 和 callerStackS
func (e *customError) genCallerInfo(skip int) {
if len(e.originErrS) == 0 {
e.callerStackMap = make(map[string]string)

// 取出调用栈50个数据
pcSlice := make([]uintptr, 50)
count := runtime.Callers(skip, pcSlice)
pcSlice = pcSlice[:count]
frames := runtime.CallersFrames(pcSlice)
var frame runtime.Frame
more := count > 0
for more {
frame, more = frames.Next()
if frame.Function == "RecoverToError" || frame.Function == "PanicError" { // 跳过
continue
}

e.callerStackS = append(e.callerStackS, frame.Function)
e.callerStackMap[frame.Function] = formatCallerLogStr(frame.Function, frame.Line)
}
}
}

// getLogStrMapList 递归获取 logStrMap
func (e *customError) getLogStrMapList() []map[string]interface{} {
var logMapList []map[string]interface{}
logMapList = append(logMapList, e.getLogStrMap())
for _, originErr := range e.originErrS {
list := originErr.getLogStrMapList()
logMapList = append(logMapList, list...)
}
return logMapList
}

// getLogStrMap 取得当前层的调用信息
func (e *customError) getLogStrMap() map[string]interface{} {
caller := ""
for _, name := range e.callerLogMsgS {
if strings.Contains(name, "RecoverToError") {
continue
}
caller += name + " | "
}

return map[string]interface{}{
"level": levelMap[e.level],https://gist.github.com/alsritter/33524c0f41777c327efd97d79c8f0692{
if endFuncName == "" || endLogStr == "" {
return
}

// 检查调用栈是否还存在信息
if len(e.callerStackS) != 0 && len(e.callerStackMap) != 0 {
var callerLogJoinSlice, endCallerFuncSlice []string

// 找到产生异常的那个方法的下标
for index, funcName := range e.callerStackS {
if funcName == endFuncName {
// 取得异常方法后面的调用方法
endCallerFuncSlice = e.callerStackS[:index]
break
}
}

// 把异常方法对应的日志添加进来
if len(endCallerFuncSlice) >= 0 {
callerLogJoinSlice = append(callerLogJoinSlice, endLogStr)
for i := len(endCallerFuncSlice) - 1; i >= 0; i-- {
callerLogJoinSlice = append(callerLogJoinSlice, e.callerStackMap[endCallerFuncSlice[i]])
}
// 设置 callerLogMsgS 为新的调用链
e.callerLogMsgS = callerLogJoinSlice
}
}

for _, err := range e.originErrS {
err.genCallerLogStr(endFuncName, endLogStr)
}
}

// getCallerFuncNameAndLogStr 获取调用者的方法名和格式化的日志字符串
func getCallerFuncNameAndLogStr(skip int) (funcName, logStr string) {
pc, _, line, ok := runtime.Caller(skip) // skip 是打印上一层调用的函数
if !ok {
return "", ""
}
funcName = runtime.FuncForPC(pc).Name()
return funcName, formatCallerLogStr(funcName, line)
}

// formatCallerLogStr 格式化调用链日志字符串
func formatCallerLogStr(funcName string, line int) string {
return fmt.Sprintf("%s: %d ", funcName, line)
}

编写 try-catch 工具

package my_error

import "fmt"

// RecoverToError 捕获执行 f 方法的异常并转成 StError(类似 Java 中的 catch 的过程)
func RecoverToError(f func()) (retErr StError) { // 这里通过具名返回值的方式传递这个 retErr
defer func() {
panicErr := recover()
if panicErr != nil {
if err, ok := panicErr.(StError); ok {
retErr = err
} else { // 非 StError 类型的错误
retErr = NewFatalErr(fmt.Sprint(panicErr))
}
}
}()
f()
return nil
}

// PanicError 向上抛异常(类似 Java 中的 throw 的过程)
func PanicError(err StError) {
if err != nil {
panic(err)
}
}

测试用例

模拟调用链

package main

import (
"fmt"
my_error "sterror/error"
)

func Pass() {
if true { // 模拟因为什么原因要抛出异常
my_error.PanicError(my_error.NewFatalErr("系统异常")) // 向上抛异常
}
}

func Pass02() {
if err := my_error.RecoverToError(Pass); err != nil {
my_error.PanicError(my_error.NewFatalErr("包装第一层", err)) // 继续向上抛异常
}
}

func Pass03() {
if err := my_error.RecoverToError(Pass02); err != nil {
my_error.PanicError(my_error.NewFatalErr("包装第二层", err))
}
}

func Pass04() {
if err := my_error.RecoverToError(Pass03); err != nil {
my_error.PanicError(my_error.NewFatalErr("包装第三层", err))
}
}

func Pass05() {
if err := my_error.RecoverToError(Pass04); err != nil {
my_error.PanicError(err)
}
}

func Pass06() {
if err := my_error.RecoverToError(Pass05); err != nil {
my_error.PanicError(err)
}
}

func Pass07() {
if err := my_error.RecoverToError(Pass06); err != nil {
fmt.Println(err.Error()) // 最终打印错误(职责链的最顶层)
}
}

// 模拟调用链
func main() {
Pass07()
}

打印输出:

[
{
"caller":"",
"level":"Fatal",
"msg":"包装第三层"
},
{
"caller":"",
"level":"Fatal",
"msg":"包装第二层"
},
{
"caller":"",
"level":"Fatal",
"msg":"包装第一层"
},
{
"caller":"main.Pass07: 46 | main.Pass06: 39 | main.Pass05: 33 | main.Pass04: 27 | main.Pass03: 21 | mai| main.Pass: 10 | ",
"level":"Fatal",
"msg":"系统异常"
}
]